import os, re, math, itertools, json
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt  
import networkx as nx  
from scipy.spatial import cKDTree  
from matplotlib import rcParams  

# ---- 中文字体配置，防止中文乱码 ----
rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei']  
rcParams['axes.unicode_minus'] = False  


# =====================工具函数（修正邻域统计参数传递）=====================
def _parse_coord_str(s):
    """解析坐标字符串（如"{x,y}"）为浮点数元组，单位转换为km"""
    if not isinstance(s, str): return None
    m = re.search(r"\{([^}]*)\}", s)  
    if not m: return None  
    parts = m.group(1).split(",")  
    if len(parts) < 2: return None  
    return (float(parts[0])/1000.0, float(parts[1])/1000.0)  


def parse_segments_from_sheet(xls_path, sheet):
    """从Excel工作表解析要素线段（道路、建筑等）或植物坐标+半径"""
    df = pd.read_excel(xls_path, sheet_name=sheet)  

    if sheet == "植物":  
        rows = []  
        for _, row in df.iterrows():  
            c = _parse_coord_str(row.iloc[0])
            v = row.iloc[1]  
            try:
                r = float(v) / 1000.0  # 半径单位转换为km
            except:
                try:
                    r = float(re.findall(r"[+-]?\d*\.?\d+", str(v))[0]) / 1000.0  
                except:
                    r = np.nan  
            if c and r == r:  
                rows.append((c[0], c[1], r))  
        return pd.DataFrame(rows, columns=["x", "y", "r"])  

    segs, cur = [], []  
    for v in df.iloc[:, 0].astype(str).tolist():  
        if re.search(r"\{0;\s*\d+\}", v):  
            if cur:  
                segs.append(cur)  
                cur = []  
            continue  
        pt = _parse_coord_str(v)  
        if pt:  
            cur.append(pt)  
    if cur:  
        segs.append(cur)  
    return segs  


def sample_points(segments, step=2.0):
    """沿线段按步长采样点（用于构建要素KD树）"""
    pts = []  
    for seg in segments:  
        pts.append(seg[0])  
        for i in range(1, len(seg)):  
            (x1, y1), (x2, y2) = seg[i - 1], seg[i]  
            dx, dy = x2 - x1, y2 - y1  
            dist = math.hypot(dx, dy)  
            n = max(1, int(dist // step))  
            for k in range(1, n + 1):  
                pts.append((x1 + dx * k / n, y1 + dy * k / n))  
    return np.array(pts) if pts else np.zeros((0, 2))  


def views_on_path(path, step=3.0):
    """沿游园路径按步长生成“观察点”（用于统计邻域元素）"""
    pts = []  
    for i in range(1, len(path)):  
        (x1, y1), (x2, y2) = path[i - 1], path[i]  
        dx, dy = x2 - x1, y2 - y1  
        dist = math.hypot(dx, dy)  
        n = max(1, int(dist // step))  
        for k in range(n):  
            t = k / n  
            pts.append((x1 + dx * t, y1 + dy * t))  
    pts.append(path[-1])  
    return pts  


def jaccard(a, b):
    """计算Jaccard距离（用于兴趣度评分）"""
    return 0.0 if (not a and not b) else 1.0 - len(a & b) / len(a | b)  


def count_elements_in_neighborhood(pt, elem_info_list, radius=12.0):
    """
    【修正】统计单个观察点邻域内四种元素的数量和占比
    :param pt: 观察点坐标(x, y)
    :param elem_info_list: 元素信息列表，每个元素为 (KD树, 总数量)
    :param radius: 邻域半径（单位km）
    :return: 字典（元素数量+占比）
    """
    result = {}
    elem_names = ["水体", "假山", "建筑", "植物"]
    for idx, (tree, total_count) in enumerate(elem_info_list):
        elem_name = elem_names[idx]
        if tree is None or total_count == 0:  # 无数据或总数量为0时，数量和占比均为0
            count = 0
            ratio = 0.0
        else:
            # 邻域内元素数量（KD树查询距离<=radius的点）
            count = len(tree.query_ball_point(pt, r=radius))
            # 占比（避免除以0）
            ratio = count / total_count if total_count > 0 else 0.0
        result[f"{elem_name}_数量"] = count
        result[f"{elem_name}_占比"] = round(ratio, 4)  # 保留4位小数
    return result  


# =====================单个园林的处理函数（核心修复）=====================
def process_garden(name, xls_path, out_dir, neighborhood_radius=12.0):
    """
    单园处理主函数：数据读取→建图→路径搜索→热点分析→路径+邻域统计→结果导出
    :param name: 园子名称
    :param xls_path: Excel数据路径
    :param out_dir: 结果输出目录
    :param neighborhood_radius: 邻域统计半径（单位km）
    :return: 园林评分指标字典
    """
    os.makedirs(out_dir, exist_ok=True)

    # ---- 1. 读取所有要素数据 ----
    roads = parse_segments_from_sheet(xls_path, "道路")          
    buildings = parse_segments_from_sheet(xls_path, "实体建筑")  
    semi_open = parse_segments_from_sheet(xls_path, "半开放建筑")
    rockeries = parse_segments_from_sheet(xls_path, "假山")      
    waters = parse_segments_from_sheet(xls_path, "水体")         
    plants = parse_segments_from_sheet(xls_path, "植物")        

    # 检查道路数据是否为空
    if not roads:
        raise ValueError(f"{name}：道路数据为空，无法构建路径图！")

    # ---- 2. 构建道路图（用于路径搜索） ----
    GRID = 0.6  # 坐标对齐网格（单位km，避免浮点误差）
    
    def snap(p, g=GRID):
        """坐标对齐：将点映射到网格节点"""
        return (round(p[0]/g)*g, round(p[1]/g)*g)
    
    G = nx.Graph()  # 无向图（道路网络）
    
    def add_edge(p, q):
        """添加带长度属性的边（避免重复边）"""
        if p == q: return
        d = math.hypot(q[0]-p[0], q[1]-p[1])  # 边长度（km）
        G.add_edge(p, q, length=d)
    
    # 道路线段对齐后添加边
    for seg in roads:
        snapped_seg = [snap(pt) for pt in seg]
        for i in range(1, len(snapped_seg)):
            add_edge(snapped_seg[i-1], snapped_seg[i])
    # 移除孤立节点（无连接的点）
    G.remove_nodes_from([n for n in list(G.nodes()) if G.degree(n) == 0])
    if G.number_of_nodes() == 0:
        raise ValueError(f"{name}：道路数据处理后无有效节点，无法构建路径图！")

    # ---- 3. 确定路径起点（入口）和终点（出口） ----
    endpoints = [n for n in G.nodes() if G.degree(n) == 1]
    if len(endpoints) < 2:
        endpoints = list(G.nodes())[:2]  # 若端点不足，取前2个节点
    
    def farthest_pair(nodes):
        """找节点列表中距离最远的一对（基于最短路径）"""
        best_pair, best_dist = None, -1
        for s in nodes:
            try:
                dist_dict = nx.single_source_dijkstra_path_length(G, s, weight="length")
            except nx.NetworkXNoPath:
                continue  # 无路径则跳过
            for t, d in dist_dict.items():
                if t in nodes and d > best_dist:
                    best_dist = d
                    best_pair = (s, t)
        return best_pair if best_pair else (nodes[0], nodes[1])
    
    entry, exit_ = farthest_pair(endpoints)  # 入口、出口

    # ---- 4. 【修正】构建元素信息列表（KD树 + 总数量） ----
    # 4.1 采样各要素的点（植物直接用原始坐标）
    water_pts = sample_points(waters, step=2.0)       # 水体采样点
    rockery_pts = sample_points(rockeries, step=2.0)  # 假山采样点
    building_pts = sample_points(buildings + semi_open, step=2.0)  # 建筑采样点
    plant_pts = np.array(plants[["x", "y"]]) if (isinstance(plants, pd.DataFrame) and not plants.empty) else np.zeros((0, 2))

    # 4.2 统计各要素总数量
    water_total = len(water_pts)
    rockery_total = len(rockery_pts)
    building_total = len(building_pts)
    plant_total = len(plant_pts)

    # 4.3 构建元素信息列表：每个元素为 (KD树, 总数量)
    elem_info_list = [
        # 水体：(KD树, 总采样点数量)
        (cKDTree(water_pts) if water_total > 0 else None, water_total),
        # 假山：(KD树, 总采样点数量)
        (cKDTree(rockery_pts) if rockery_total > 0 else None, rockery_total),
        # 建筑：(KD树, 总采样点数量)
        (cKDTree(building_pts) if building_total > 0 else None, building_total),
        # 植物：(KD树, 总数量)
        (cKDTree(plant_pts) if plant_total > 0 else None, plant_total)
    ]

    # ---- 5. 兴趣度评分与最佳路径搜索 ----
    def visible_set(pt, R=neighborhood_radius):
        """获取观察点邻域内存在的要素类型集合（用于兴趣度计算）"""
        cats = set()
        elem_names = ["水体", "假山", "建筑", "植物"]
        for idx, (tree, total) in enumerate(elem_info_list):
            if tree and len(tree.query_ball_point(pt, r=R)) > 0:
                cats.add(elem_names[idx])
        return cats
    
    def interest_score(path):
        """计算路径的兴趣度评分及相关指标"""
        vpts = [path[0]] + views_on_path(path, step=3.0)
        vis_sets = [visible_set(p) for p in vpts]
        changes = [jaccard(vis_sets[i-1], vis_sets[i]) for i in range(1, len(vis_sets))]
        mean_change = float(np.mean(changes)) if changes else 0.0
        
        # 计算路径方向（用于统计转弯）
        headings = []
        for i in range(1, len(path)):
            dx = path[i][0] - path[i-1][0]
            dy = path[i][1] - path[i-1][1]
            if dx == 0 and dy == 0:
                continue  # 跳过零长度段
            headings.append(math.atan2(dy, dx))
        
        # 统计转弯次数（角度>20度视为转弯）
        turns = 0
        for i in range(1, len(headings)):
            angle_diff = abs((headings[i] - headings[i-1] + math.pi) % (2 * math.pi) - math.pi)
            if angle_diff > math.radians(20):
                turns += 1
        
        # 统计交汇点数量
        inters = sum(1 for n in path if G.degree(n) >= 3)
        
        # 计算兴趣度评分
        path_segments = max(1, len(path) - 1)  # 路径段数（避免除以0）
        score = (0.45 * mean_change + 
                 0.30 * (turns / path_segments) + 
                 0.25 * (inters / max(1, len(path))))
        return score, mean_change, vpts, vis_sets, turns, inters

    # 路径搜索：枚举长度≤1.8倍最短路径的所有可能路径
    try:
        shortest_path_len = nx.shortest_path_length(G, entry, exit_, weight="length")
    except nx.NetworkXNoPath:
        raise ValueError(f"{name}：入口{entry}到出口{exit_}无有效路径！")
    
    max_path_len = 1.8 * shortest_path_len  # 路径长度上限
    
    def enum_paths(G, start, end, max_len):
        """深度优先枚举符合长度限制的路径"""
        stack = [(start, [start], 0.0)]
        while stack:
            curr_node, curr_path, curr_len = stack.pop()
            if curr_node == end:
                yield curr_path
                continue
            for neighbor in G.neighbors(curr_node):
                if neighbor not in curr_path:
                    new_len = curr_len + G[curr_node][neighbor]["length"]
                    if new_len <= max_len:
                        stack.append((neighbor, curr_path + [neighbor], new_len))
    
    # 筛选最佳路径（兴趣度最高）
    best_score = -1
    best_result = None
    path_count = 0
    for path in enum_paths(G, entry, exit_, max_path_len):
        path_count += 1
        if path_count > 8000:  # 限制最大枚举数，避免性能问题
            break
        score, mc, vpts, vs, turns, inters = interest_score(path)
        if score > best_score:
            best_score = score
            best_result = (score, mc, vpts, vs, path, turns, inters)
    
    # 若未找到枚举路径，使用最短路径
    if not best_result:
        shortest_path = nx.shortest_path(G, entry, exit_, weight="length")
        score, mc, vpts, vs, turns, inters = interest_score(shortest_path)
        best_result = (score, mc, vpts, vs, shortest_path, turns, inters)
    
    score, mean_change, view_pts, vis_sets, best_path, turns, inters = best_result

    # ---- 6. 导出完整游园路径 ----
    path_data = []
    cumulative_dist = 0.0
    path_data.append({
        "园林名称": name,
        "路径节点序号": 1,
        "X坐标(km)": round(best_path[0][0], 4),
        "Y坐标(km)": round(best_path[0][1], 4),
        "累计距离(km)": round(cumulative_dist, 4),
        "节点类型": "入口",
        "节点度数": G.degree(best_path[0])
    })
    
    for idx in range(1, len(best_path)):
        prev_x, prev_y = best_path[idx-1]
        curr_x, curr_y = best_path[idx]
        # 累加距离
        segment_dist = math.hypot(curr_x - prev_x, curr_y - prev_y)
        cumulative_dist += segment_dist
        # 节点类型（出口/途经点）
        node_type = "出口" if idx == len(best_path)-1 else "途经点"
        path_data.append({
            "园林名称": name,
            "路径节点序号": idx + 1,
            "X坐标(km)": round(curr_x, 4),
            "Y坐标(km)": round(curr_y, 4),
            "累计距离(km)": round(cumulative_dist, 4),
            "节点类型": node_type,
            "节点度数": G.degree(best_path[idx])
        })
    
    path_df = pd.DataFrame(path_data)
    path_df = pd.DataFrame(path_data)
    path_df.to_csv(
        os.path.join(out_dir, f"{name}_完整游园路径.csv"),
        index=False,
        encoding="utf-8-sig"
    )

    # ---- 7. 导出观察点邻域元素统计（调用修正后的函数） ----
    neighborhood_data = []
    elem_names = ["水体", "假山", "建筑", "植物"]
    for view_idx, (pt, vis_set) in enumerate(zip(view_pts, vis_sets)):
        x, y = pt
        # 【关键修正】传入 elem_info_list（包含KD树和总数量）
        elem_stats = count_elements_in_neighborhood(
            pt=pt,
            elem_info_list=elem_info_list,
            radius=neighborhood_radius
        )
        # 计算观察点到入口的直线距离
        dist_to_entry = math.hypot(x - entry[0], y - entry[1])
        # 整理数据
        neighborhood_row = {
            "园林名称": name,
            "观察点序号": view_idx + 1,
            "X坐标(km)": round(x, 4),
            "Y坐标(km)": round(y, 4),
            "到入口距离(km)": round(dist_to_entry, 4),
            "可见要素类型": ",".join(vis_set) if vis_set else "无"
        }
        # 补充各元素的数量和占比（转为百分比更直观）
        for elem_name in elem_names:
            neighborhood_row[f"{elem_name}数量"] = elem_stats[f"{elem_name}_数量"]
            neighborhood_row[f"{elem_name}占比(%)"] = round(elem_stats[f"{elem_name}_占比"] * 100, 2)
        neighborhood_data.append(neighborhood_row)
    
    neighborhood_df = pd.DataFrame(neighborhood_data)
    neighborhood_df.to_csv(
        os.path.join(out_dir, f"{name}_观察点邻域元素统计.csv"),
        index=False,
        encoding="utf-8-sig"
    )

    # ---- 8. 导出要素总览（核对占比计算依据） ----
    total_elem_data = {
        "园林名称": name,
        "邻域统计半径(km)": neighborhood_radius,
        "水体总采样点数量": elem_info_list[0][1],
        "假山总采样点数量": elem_info_list[1][1],
        "建筑总采样点数量": elem_info_list[2][1],
        "植物总数量": elem_info_list[3][1]
    }
    pd.DataFrame([total_elem_data]).to_csv(
        os.path.join(out_dir, f"{name}_要素总览.csv"),
        index=False,
        encoding="utf-8-sig"
    )

    # ---- 9. 异景热点分析与导出 ----
    # 计算观察点间要素变化率
    changes = [0.0]  # 第一个观察点无前置对比，变化率为0
    for i in range(1, len(vis_sets)):
        changes.append(jaccard(vis_sets[i-1], vis_sets[i]))
    changes = np.array(changes)
    view_pts_arr = np.array(view_pts)

    # 筛选热点（最多10个，间隔≥5个观察点避免密集）
    picked_idx = []
    # 按变化率降序排序的索引
    sorted_idx = np.argsort(changes)[::-1]
    for idx in sorted_idx:
        if len(picked_idx) >= 10:
            break
        # 确保与已选热点间隔≥5个观察点
        if all(abs(idx - existing_idx) >= 5 for existing_idx in picked_idx):
            picked_idx.append(idx)
    
    # 整理热点数据
    hotspot_data = []
    for hot_rank, idx in enumerate(picked_idx, 1):
        x, y = view_pts_arr[idx]
        hotspot_data.append({
            "园林名称": name,
            "热点序号": f"H{hot_rank}",
            "对应观察点序号": idx + 1,
            "X坐标(km)": round(x, 4),
            "Y坐标(km)": round(y, 4),
            "要素变化率": round(changes[idx], 4),
            "变化率排名": hot_rank
        })
    
    pd.DataFrame(hotspot_data).to_csv(
        os.path.join(out_dir, f"{name}_异景热点详情.csv"),
        index=False,
        encoding="utf-8-sig"
    )

    # ---- 10. 可视化绘图（保留原逻辑，优化标签显示） ----
    # 计算坐标范围（基于道路数据，避免要素超出视图）
    all_road_x = []
    all_road_y = []
    for seg in roads:
        seg_x = [p[0] for p in seg]
        seg_y = [p[1] for p in seg]
        all_road_x.extend(seg_x)
        all_road_y.extend(seg_y)
    xmin, xmax = min(all_road_x), max(all_road_x)
    ymin, ymax = min(all_road_y), max(all_road_y)
    # 增加边距（5%），避免要素贴边
    pad_x = (xmax - xmin) * 0.05 if (xmax - xmin) != 0 else 1.0
    pad_y = (ymax - ymin) * 0.05 if (ymax - ymin) != 0 else 1.0
    xlim = (xmin - pad_x, xmax + pad_x)
    ylim = (ymin - pad_y, ymax + pad_y)

    # 创建画布
    fig, ax = plt.subplots(figsize=(12, 8))
    # 1. 绘制道路网络（灰色细线条，透明度0.7）
    for seg in roads:
        seg_x = [p[0] for p in seg]
        seg_y = [p[1] for p in seg]
        ax.plot(seg_x, seg_y, color="#999999", linewidth=1.2, alpha=0.7, 
                label="道路网络" if seg == roads[0] else "")
    # 2. 绘制最佳游线（深蓝色粗线条，突出显示）
    path_x = [p[0] for p in best_path]
    path_y = [p[1] for p in best_path]
    ax.plot(path_x, path_y, color="#1f77b4", linewidth=2.5, alpha=0.9, label="最佳游线")
    # 3. 标记入口和出口（不同形状+颜色，避免混淆）
    ax.scatter(entry[0], entry[1], s=100, marker="o", color="#2ca02c", edgecolor="black", 
               linewidth=1.5, label="入口", zorder=5)
    ax.scatter(exit_[0], exit_[1], s=100, marker="s", color="#ff7f0e", edgecolor="black", 
               linewidth=1.5, label="出口", zorder=5)
    # 入口/出口标签（微调位置，避免遮挡）
    ax.text(entry[0] + pad_x*0.5, entry[1] + pad_y*0.5, "入口", fontsize=10, 
            color="#2ca02c", fontweight="bold")
    ax.text(exit_[0] + pad_x*0.5, exit_[1] + pad_y*0.5, "出口", fontsize=10, 
            color="#ff7f0e", fontweight="bold")
    # 4. 绘制异景热点（红色叉号+标签）
    if len(picked_idx) > 0:
        hot_pts_arr = view_pts_arr[picked_idx]
        ax.scatter(hot_pts_arr[:, 0], hot_pts_arr[:, 1], s=80, marker="x", color="#d62728", 
                   linewidth=2.0, label="异景热点", zorder=4)
        # 热点标签（显示序号和变化率）
        for hot_rank, (x, y) in enumerate(hot_pts_arr, 1):
            change_rate = changes[picked_idx[hot_rank-1]]
            ax.text(x + pad_x*0.3, y + pad_y*0.3, f"H{hot_rank}\nΔ={change_rate:.2f}", 
                    fontsize=8, color="#d62728", fontweight="bold", ha="left")

    # 图表美化设置
    ax.set_title(f"{name} 最佳游园路径与异景热点分布", fontsize=16, pad=20, fontweight="bold")
    ax.set_xlabel("X 坐标 (km)", fontsize=12, labelpad=10)
    ax.set_ylabel("Y 坐标 (km)", fontsize=12, labelpad=10)
    ax.set_xlim(xlim)
    ax.set_ylim(ylim)
    ax.set_aspect("equal", adjustable="box")  # 等比例显示，避免坐标拉伸
    ax.grid(True, alpha=0.3, linestyle="--")  # 网格线（虚线，低透明度）
    ax.legend(loc="upper right", fontsize=10, framealpha=0.9)  # 图例（半透明背景）
    plt.tight_layout()  # 自动调整布局，避免标签截断

    # 保存图片（高分辨率，避免边缘截断）
    plt.savefig(
        os.path.join(out_dir, f"{name}_最佳游线图.png"),
        dpi=300,  # 300dpi保证清晰度
        bbox_inches="tight",  # 裁剪边缘空白
        facecolor="white"  # 背景色为白色（避免透明背景）
    )
    plt.close()

    # ---- 11. 导出园林评分指标 ----
    # 计算最佳路径总长度
    total_path_len = 0.0
    for i in range(1, len(best_path)):
        dx = best_path[i][0] - best_path[i-1][0]
        dy = best_path[i][1] - best_path[i-1][1]
        total_path_len += math.hypot(dx, dy)
    
    index_data = {
        "园林名称": name,
        "兴趣度评分": round(score, 4),
        "要素平均变化率": round(mean_change, 4),
        "路径总转弯数": turns,
        "路径交汇点数量": inters,
        "最佳路径节点数": len(best_path),
        "最佳路径总长度(km)": round(total_path_len, 4),
        "观察点总数": len(view_pts),
        "异景热点数量": len(picked_idx)
    }
    pd.DataFrame([index_data]).to_csv(
        os.path.join(out_dir, f"{name}_评分指标.csv"),
        index=False,
        encoding="utf-8-sig"
    )

    print(f"✅ {name} 处理完成：共生成6类文件（路径+统计+图表）")
    return index_data


# =====================主程序：批量处理十座园林（增加异常捕获）=====================
if __name__ == "__main__":
    # 1. 配置园林数据路径（需确保data文件夹与代码同级）
    file_map = {  
        "拙政园": r"data/拙政园数据坐标.xlsx",
        "留园": r"data/留园数据坐标.xlsx",
        "寄畅园": r"data/寄畅园数据坐标.xlsx",
        "瞻园": r"data/瞻园数据坐标.xlsx",
        "豫园": r"data/豫园数据坐标.xlsx",
        "秋霞园": r"data/秋霞园数据坐标.xlsx",
        "沈园": r"data/沈园数据坐标.xlsx",
        "怡园": r"data/怡园数据坐标.xlsx",
        "耦园": r"data/耦园数据坐标.xlsx",
        "绮园": r"data/绮园数据坐标.xlsx"
    }

    # 2. 配置输出目录（自动创建，避免手动新建）
    out_dir = "./园林游园分析结果_最终版"
    os.makedirs(out_dir, exist_ok=True)
    print(f"📁 结果输出目录：{os.path.abspath(out_dir)}")

    # 3. 批量处理所有园林（增加异常捕获，避免单个园林失败导致整体中断）
    all_garden_index = []  # 存储所有园林的评分指标，用于汇总
    failed_gardens = []    # 存储处理失败的园林名称，便于排查

    for garden_name, xls_path in file_map.items():
        print(f"\n🔄 开始处理：{garden_name}")
        # 检查Excel文件是否存在
        if not os.path.exists(xls_path):
            print(f"❌ {garden_name} 处理失败：文件不存在 → {xls_path}")
            failed_gardens.append(garden_name)
            continue
        
        try:
            # 调用核心处理函数
            garden_index = process_garden(
                name=garden_name,
                xls_path=xls_path,
                out_dir=out_dir,
                neighborhood_radius=12.0  # 邻域统计半径（可根据需求调整，默认12km）
            )
            all_garden_index.append(garden_index)
        
        except Exception as e:
            # 捕获所有异常，记录失败原因
            error_msg = str(e)[:100]  # 截取前100字符，避免日志过长
            print(f"❌ {garden_name} 处理失败：{error_msg}...")
            failed_gardens.append(f"{garden_name}（原因：{error_msg}）")
            continue

    # 4. 导出所有园林的评分汇总表
    if all_garden_index:
        summary_df = pd.DataFrame(all_garden_index)
        # 按兴趣度评分降序排序，便于对比
        summary_df = summary_df.sort_values(by="兴趣度评分", ascending=False).reset_index(drop=True)
        summary_df.to_csv(
            os.path.join(out_dir, "十园评分指标汇总表.csv"),
            index=False,
            encoding="utf-8-sig"
        )
        print(f"\n📊 汇总表生成完成：{os.path.abspath(os.path.join(out_dir, '十园评分指标汇总表.csv'))}")
    
    # 5. 输出处理结果总结
    print(f"\n==================== 处理结果总结 ====================")
    print(f"✅ 成功处理园林数量：{len(all_garden_index)} 座")
    print(f"❌ 失败处理园林数量：{len(failed_gardens)} 座")
    if failed_gardens:
        print(f"📝 失败列表：{', '.join(failed_gardens)}")
    print(f"======================================================")